export function randomFromInterval(min: number, max: number) {
    // min and max included
    return Math.random() * (max - min) + min;
}

function cubicBezier(P0: number[], P1: number[], P2: number[], P3: number[], t: number) {
    var x = (1 - t) ** 3 * P0[0] + 3 * (1 - t) ** 2 * t * P1[0] + 3 * (1 - t) * t ** 2 * P2[0] + t ** 3 * P3[0];
    var y = (1 - t) ** 3 * P0[1] + 3 * (1 - t) ** 2 * t * P1[1] + 3 * (1 - t) * t ** 2 * P2[1] + t ** 3 * P3[1];
    return [x, y];
}

function generateEyeParameters(width: number) {
    let height_upper = Math.random() * width / 1.2;// Less height for the upper eyelid to make it sharper
    let height_lower = Math.random() * width / 1.2;// More height for the lower eyelid to make it rounder and droopier
    let P0_upper_randX = Math.random() * 0.4 - 0.2;
    let P3_upper_randX = Math.random() * 0.4 - 0.2;
    let P0_upper_randY = Math.random() * 0.4 - 0.2;
    let P3_upper_randY = Math.random() * 0.4 - 0.2;
    let offset_upper_left_randY = Math.random();
    let offset_upper_right_randY = Math.random();
    let P0_upper = [-width / 2 + P0_upper_randX * width / 16, P0_upper_randY * height_upper / 16];
    let P3_upper = [width / 2 + P3_upper_randX * width / 16, P3_upper_randY * height_upper / 16];
    let P0_lower = P0_upper;// Starting at the same point as the upper eyelid
    let P3_lower = P3_upper;// Ending at the same point as the upper eyelid
    let eye_true_width = P3_upper[0] - P0_upper[0];

    let offset_upper_left_x = randomFromInterval(-eye_true_width / 10.0, eye_true_width / 2.3);// Upper eyelid control point offset to create asymmetry
    let offset_upper_right_x = randomFromInterval(-eye_true_width / 10.0, eye_true_width / 2.3);// Upper eyelid control point offset to create asymmetry
    let offset_upper_left_y = offset_upper_left_randY * height_upper;// Upper eyelid control point offset to create asymmetry
    let offset_upper_right_y = offset_upper_right_randY * height_upper;// Upper eyelid control point offset to create asymmetry
    let offset_lower_left_x = randomFromInterval(offset_upper_left_x, eye_true_width / 2.1);// Lower eyelid control point offset
    let offset_lower_right_x = randomFromInterval(offset_upper_right_x, eye_true_width / 2.1);// Upper eyelid control point offset to create asymmetry
    let offset_lower_left_y = randomFromInterval(-offset_upper_left_y + 5, height_lower);// Upper eyelid control point offset to create asymmetry
    let offset_lower_right_y = randomFromInterval(-offset_upper_right_y + 5, height_lower);// Upper eyelid control point offset to create asymmetry
    // Generate points for the Bezier curves
    let left_converge0 = Math.random();
    let right_converge0 = Math.random();
    // Generate points for the Bezier curves
    let left_converge1 = Math.random();
    let right_converge1 = Math.random();
    return {
        height_upper: height_upper,
        height_lower: height_lower,
        P0_upper_randX: P0_upper_randX,
        P3_upper_randX: P3_upper_randX,
        P0_upper_randY: P0_upper_randY,
        P3_upper_randY: P3_upper_randY,
        offset_upper_left_randY: offset_upper_left_randY,
        offset_upper_right_randY: offset_upper_right_randY,
        eye_true_width: eye_true_width,
        offset_upper_left_x: offset_upper_left_x,
        offset_upper_right_x: offset_upper_right_x,
        offset_upper_left_y: offset_upper_left_y,
        offset_upper_right_y: offset_upper_right_y,
        offset_lower_left_x: offset_lower_left_x,
        offset_lower_right_x: offset_lower_right_x,
        offset_lower_left_y: offset_lower_left_y,
        offset_lower_right_y: offset_lower_right_y,
        left_converge0: left_converge0,
        right_converge0: right_converge0,
        left_converge1: left_converge1,
        right_converge1: right_converge1
    }
}

export function generateEyePoints(rands: any, width = 50) {

    let P0_upper = [-width / 2 + rands.P0_upper_randX * width / 16, rands.P0_upper_randY * rands.height_upper / 16];
    let P3_upper = [width / 2 + rands.P3_upper_randX * width / 16, rands.P3_upper_randY * rands.height_upper / 16];
    let P0_lower = P0_upper;// Starting at the same point as the upper eyelid
    let P3_lower = P3_upper;// Ending at the same point as the upper eyelid
    let eye_true_width = P3_upper[0] - P0_upper[0];

    // Upper eyelid control points
    let P1_upper = [P0_upper[0] + rands.offset_upper_left_x, P0_upper[1] + rands.offset_upper_left_y];  // First control point
    let P2_upper = [P3_upper[0] - rands.offset_upper_right_x, P3_upper[1] + rands.offset_upper_right_y];  // Second control point


    // Lower eyelid control points
    let P1_lower = [P0_lower[0] + rands.offset_lower_left_x, P0_lower[1] - rands.offset_lower_left_y];  // First control point
    let P2_lower = [P3_lower[0] - rands.offset_lower_right_x, P3_lower[1] - rands.offset_lower_right_y];  // Second control point

    // now we generate the points for the upper eyelid
    let upper_eyelid_points = [];
    let upper_eyelid_points_left_control = [];
    let upper_eyelid_points_right_control = [];
    let upper_eyelid_left_control_point = [P0_upper[0] * (1 - rands.left_converge0) + P1_lower[0] * rands.left_converge0, P0_upper[1] * (1 - rands.left_converge0) + P1_lower[1] * rands.left_converge0];
    let upper_eyelid_right_control_point = [P3_upper[0] * (1 - rands.right_converge0) + P2_lower[0] * rands.right_converge0, P3_upper[1] * (1 - rands.right_converge0) + P2_lower[1] * rands.right_converge0];
    for (let t = 0; t < 100; t++) {
        upper_eyelid_points.push(cubicBezier(P0_upper, P1_upper, P2_upper, P3_upper, t / 100));
        upper_eyelid_points_left_control.push(cubicBezier(upper_eyelid_left_control_point, P0_upper, P1_upper, P2_upper, t / 100));
        upper_eyelid_points_right_control.push(cubicBezier(P1_upper, P2_upper, P3_upper, upper_eyelid_right_control_point, t / 100));
    }

    for (let i = 0; i < 75; i++) {
        let weight = ((75.0 - i) / 75.0) ** 2
        upper_eyelid_points[i] = [upper_eyelid_points[i][0] * (1 - weight) + upper_eyelid_points_left_control[i + 25][0] * weight, upper_eyelid_points[i][1] * (1 - weight) + upper_eyelid_points_left_control[i + 25][1] * weight]
        upper_eyelid_points[i + 25] = [upper_eyelid_points[i + 25][0] * weight + upper_eyelid_points_right_control[i][0] * (1 - weight), upper_eyelid_points[i + 25][1] * weight + upper_eyelid_points_right_control[i][1] * (1 - weight)]
    }


    // now we generate the points for the upper eyelid
    let lower_eyelid_points = [];
    let lower_eyelid_points_left_control = [];
    let lower_eyelid_points_right_control = [];
    let lower_eyelid_left_control_point = [P0_lower[0] * (1 - rands.left_converge0) + P1_upper[0] * rands.left_converge0, P0_lower[1] * (1 - rands.left_converge0) + P1_upper[1] * rands.left_converge0];
    let lower_eyelid_right_control_point = [P3_lower[0] * (1 - rands.right_converge1) + P2_upper[0] * rands.right_converge1, P3_lower[1] * (1 - rands.right_converge1) + P2_upper[1] * rands.right_converge1];
    for (let t = 0; t < 100; t++) {
        lower_eyelid_points.push(cubicBezier(P0_lower, P1_lower, P2_lower, P3_lower, t / 100));
        lower_eyelid_points_left_control.push(cubicBezier(lower_eyelid_left_control_point, P0_lower, P1_lower, P2_lower, t / 100));
        lower_eyelid_points_right_control.push(cubicBezier(P1_lower, P2_lower, P3_lower, lower_eyelid_right_control_point, t / 100));
    }

    for (let i = 0; i < 75; i++) {
        let weight = ((75.0 - i) / 75.0) ** 2
        lower_eyelid_points[i] = [lower_eyelid_points[i][0] * (1 - weight) + lower_eyelid_points_left_control[i + 25][0] * weight, lower_eyelid_points[i][1] * (1 - weight) + lower_eyelid_points_left_control[i + 25][1] * weight]
        lower_eyelid_points[i + 25] = [lower_eyelid_points[i + 25][0] * weight + lower_eyelid_points_right_control[i][0] * (1 - weight), lower_eyelid_points[i + 25][1] * weight + lower_eyelid_points_right_control[i][1] * (1 - weight)]
    }
    for (let i = 0; i < 100; i++) {
        lower_eyelid_points[i][1] = -lower_eyelid_points[i][1]
        upper_eyelid_points[i][1] = -upper_eyelid_points[i][1]
    }

    let eyeCenter = [upper_eyelid_points[50][0] / 2.0 + lower_eyelid_points[50][0] / 2.0, upper_eyelid_points[50][1] / 2.0 + lower_eyelid_points[50][1] / 2.0];

    for (let i = 0; i < 100; i++) {
        // translate to center
        lower_eyelid_points[i][0] -= eyeCenter[0]
        lower_eyelid_points[i][1] -= eyeCenter[1]
        upper_eyelid_points[i][0] -= eyeCenter[0]
        upper_eyelid_points[i][1] -= eyeCenter[1]
    }
    eyeCenter = [0, 0];

    // we switch the upper and lower eyelid points because in svg the bottom is y+ and top is y-
    return { upper: upper_eyelid_points, lower: lower_eyelid_points, center: [eyeCenter] }
}

export function generateBothEyes(width = 50) {
    let rands_left = generateEyeParameters(width)
    // Create a shallow copy of the object
    let rands_right = { ...rands_left } as any;

    // Iterate over the object's keys
    for (let key in rands_right) {
        // Check if the property value is a number
        if (typeof rands_right[key] === 'number') {
            // Add a random value to the number, for example, between -5 and 5
            rands_right[key] += randomFromInterval(-rands_right[key] / 2.0, rands_right[key] / 2.0);
        }
    }
    let left_eye = generateEyePoints(rands_left, width) as any
    let right_eye = generateEyePoints(rands_right, width)

    for (let key in left_eye) {
        if (typeof left_eye[key] === 'object') {
            for (let i = 0; i < left_eye[key].length; i++) {
                left_eye[key][i][0] = -left_eye[key][i][0]
            }
        }
    }
    return { left: left_eye, right: right_eye }
}



export function getEggShapePoints(a: number, b: number, k: number, segment_points: number) {
    // the function is x^2/a^2 * (1 + ky) + y^2/b^2 = 1
    var result = [];
    //   var pointString = "";
    for (var i = 0; i < segment_points; i++) {
        // x positive, y positive
        // first compute the degree
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 1.1 / segment_points,
                Math.PI / 1.1 / segment_points,
            );
        var y = Math.sin(degree) * b;
        var x =
            Math.sqrt(((1 - (y * y) / (b * b)) / (1 + k * y)) * a * a) +
            randomFromInterval(-a / 200.0, a / 200.0);
        // pointString += x + "," + y + " ";
        result.push([x, y]);
    }
    for (var i = segment_points; i > 0; i--) {
        // x is negative, y is positive
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 1.1 / segment_points,
                Math.PI / 1.1 / segment_points,
            );
        var y = Math.sin(degree) * b;
        var x =
            -Math.sqrt(((1 - (y * y) / (b * b)) / (1 + k * y)) * a * a) +
            randomFromInterval(-a / 200.0, a / 200.0);
        // pointString += x + "," + y + " ";
        result.push([x, y]);
    }
    for (var i = 0; i < segment_points; i++) {
        // x is negative, y is negative
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 1.1 / segment_points,
                Math.PI / 1.1 / segment_points,
            );
        var y = -Math.sin(degree) * b;
        var x =
            -Math.sqrt(((1 - (y * y) / (b * b)) / (1 + k * y)) * a * a) +
            randomFromInterval(-a / 200.0, a / 200.0);
        // pointString += x + "," + y + " ";
        result.push([x, y]);
    }
    for (var i = segment_points; i > 0; i--) {
        // x is positive, y is negative
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 1.1 / segment_points,
                Math.PI / 1.1 / segment_points,
            );
        var y = -Math.sin(degree) * b;
        var x =
            Math.sqrt(((1 - (y * y) / (b * b)) / (1 + k * y)) * a * a) +
            randomFromInterval(-a / 200.0, a / 200.0);
        // pointString += x + "," + y + " ";
        result.push([x, y]);
    }
    return result;
}

function findIntersectionPoints(radian: number, a: number, b: number) {
    if (radian < 0) {
        radian = 0;
    }
    if (radian > Math.PI / 2) {
        radian = Math.PI / 2;
    }
    // a is width, b is height
    // Slope of the line
    const m = Math.tan(radian);
    // check if radian is close to 90 degrees
    if (Math.abs(radian - Math.PI / 2) < 0.0001) {
        return { x: 0, y: b };
    }
    // only checks the first quadrant
    const y = m * a;
    if (y < b) {
        // it intersects with the left side
        return { x: a, y: y };
    } else {
        // it intersects with the top side
        // console.log(m);
        const x = b / m;
        // console.log(x, b);
        return { x: x, y: b };
    }
}

export function generateRectangularFaceContourPoints(a: number, b: number, segment_points: number) {
    // a is width, b is height, segment_points is the number of points

    var result = [];
    for (var i = 0; i < segment_points; i++) {
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 11 / segment_points,
                Math.PI / 11 / segment_points,
            );
        var intersection = findIntersectionPoints(degree, a, b);
        result.push([intersection.x, intersection.y]);
    }
    for (var i = segment_points; i > 0; i--) {
        // x is negative, y is positive
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 11 / segment_points,
                Math.PI / 11 / segment_points,
            );
        var intersection = findIntersectionPoints(degree, a, b);
        result.push([-intersection.x, intersection.y]);
    }
    for (var i = 0; i < segment_points; i++) {
        // x is negative, y is negative
        // first compute the degree
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 11 / segment_points,
                Math.PI / 11 / segment_points,
            );
        var intersection = findIntersectionPoints(degree, a, b);
        result.push([-intersection.x, -intersection.y]);
    }
    for (var i = segment_points; i > 0; i--) {
        // x is positive, y is negative
        // first compute the degree
        var degree =
            (Math.PI / 2 / segment_points) * i +
            randomFromInterval(
                -Math.PI / 11 / segment_points,
                Math.PI / 11 / segment_points,
            );
        var intersection = findIntersectionPoints(degree, a, b);
        result.push([intersection.x, -intersection.y]);
    }
    return result;
}

export function generateFaceCountourPoints(numPoints = 100) {
    var faceSizeX0 = randomFromInterval(50, 100);
    var faceSizeY0 = randomFromInterval(70, 100);

    var faceSizeY1 = randomFromInterval(50, 80);
    var faceSizeX1 = randomFromInterval(70, 100);
    var faceK0 =
        randomFromInterval(0.001, 0.005) * (Math.random() > 0.5 ? 1 : -1);
    var faceK1 =
        randomFromInterval(0.001, 0.005) * (Math.random() > 0.5 ? 1 : -1);
    var face0TranslateX = randomFromInterval(-5, 5);
    var face0TranslateY = randomFromInterval(-15, 15);

    var face1TranslateY = randomFromInterval(-5, 5);
    var face1TranslateX = randomFromInterval(-5, 25);
    var eggOrRect0 = Math.random() > 0.1;
    var eggOrRect1 = Math.random() > 0.3;

    var results0 = eggOrRect0
        ? getEggShapePoints(faceSizeX0, faceSizeY0, faceK0, numPoints)
        : generateRectangularFaceContourPoints(faceSizeX0, faceSizeY0, numPoints);
    var results1 = eggOrRect1
        ? getEggShapePoints(faceSizeX1, faceSizeY1, faceK1, numPoints)
        : generateRectangularFaceContourPoints(faceSizeX1, faceSizeY1, numPoints);
    for (var i = 0; i < results0.length; i++) {
        results0[i][0] += face0TranslateX;
        results0[i][1] += face0TranslateY;
        results1[i][0] += face1TranslateX;
        results1[i][1] += face1TranslateY;
    }
    var results = [];
    let center = [0, 0];
    for (var i = 0; i < results0.length; i++) {
        results.push([
            results0[i][0] * 0.7 +
            results1[(i + results0.length / 4) % results0.length][1] * 0.3,
            results0[i][1] * 0.7 -
            results1[(i + results0.length / 4) % results0.length][0] * 0.3,
        ]);
        center[0] += results[i][0];
        center[1] += results[i][1];
    }
    center[0] /= results.length;
    center[1] /= results.length;
    // center the face
    for (var i = 0; i < results.length; i++) {
        results[i][0] -= center[0];
        results[i][1] -= center[1];
    }

    let width = results[0][0] - results[results.length / 2][0];
    let height =
        results[results.length / 4][1] - results[(results.length * 3) / 4][1];
    // add the first point to the end to close the shape
    results.push(results[0]);
    results.push(results[1]);
    // console.log(results);
    return { face: results, width: width, height: height, center: [0, 0] };
}



function factorial(n: number): number {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

function binomialCoefficient(n: number, k: number) {
    return factorial(n) / (factorial(k) * factorial(n - k));
}

function calculateBezierPoint(t: number, controlPoints: string | any[]) {
    let x = 0, y = 0;
    const n = controlPoints.length - 1;

    for (let i = 0; i <= n; i++) {
        let binCoeff = binomialCoefficient(n, i);
        let a = Math.pow(1 - t, n - i);
        let b = Math.pow(t, i);
        x += binCoeff * a * b * controlPoints[i].x;
        y += binCoeff * a * b * controlPoints[i].y;
    }

    return [x, y];
}

function computeBezierCurve(controlPoints: string | any[], numberOfPoints: number) {
    let curve = [];
    for (let i = 0; i <= numberOfPoints; i++) {
        let t = i / numberOfPoints;
        let point = calculateBezierPoint(t, controlPoints);
        curve.push(point);
    }
    return curve;
}

export function generateHairLines0(faceCountour: string | any[], numHairLines = 100) {
    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    var results = [];
    for (var i = 0; i < numHairLines; i++) {
        var numHairPoints = 20 + Math.floor(randomFromInterval(-5, 5));
        // we generate some hair lines
        var hair_line = [];
        var index_offset = Math.floor(randomFromInterval(30, 140));
        for (var j = 0; j < numHairPoints; j++) {
            hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - (j + index_offset)) % faceCountourCopy.length][0], y: faceCountourCopy[(faceCountourCopy.length - (j + index_offset)) % faceCountourCopy.length][1] });
        }
        var d0 = computeBezierCurve(hair_line, numHairPoints);
        hair_line = []
        index_offset = Math.floor(randomFromInterval(30, 140));
        for (var j = 0; j < numHairPoints; j++) {
            hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - (-j + index_offset)) % faceCountourCopy.length][0], y: faceCountourCopy[(faceCountourCopy.length - (-j + index_offset)) % faceCountourCopy.length][1] });
        }
        var d1 = computeBezierCurve(hair_line, numHairPoints);
        var d = [];
        for (var j = 0; j < numHairPoints; j++) {
            d.push([d0[j][0] * (j * (1 / numHairPoints)) ** 2 + d1[j][0] * (1 - (j * (1 / numHairPoints)) ** 2), d0[j][1] * (j * (1 / numHairPoints)) ** 2 + d1[j][1] * (1 - (j * (1 / numHairPoints)) ** 2)]);
        }

        results.push(d);
    }
    return results;
}
export function generateHairLines1(faceCountour: string | any[], numHairLines = 100) {
    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    var results = [];
    for (var i = 0; i < numHairLines; i++) {
        var numHairPoints = 20 + Math.floor(randomFromInterval(-5, 5));
        // we generate some hair lines
        var hair_line = [];
        var index_start = Math.floor(randomFromInterval(20, 160));
        hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - index_start) % faceCountourCopy.length][0], y: faceCountourCopy[(faceCountourCopy.length - index_start) % faceCountourCopy.length][1] });

        for (var j = 1; j < numHairPoints + 1; j++) {
            index_start = Math.floor(randomFromInterval(20, 160));
            hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - index_start) % faceCountourCopy.length][0], y: faceCountourCopy[(faceCountourCopy.length - index_start) % faceCountourCopy.length][1] });
        }
        var d = computeBezierCurve(hair_line, numHairPoints);

        results.push(d);
    }
    return results;
}


export function generateHairLines2(faceCountour: string | any[], numHairLines = 100) {

    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    var results = [];
    var pickedIndices = [];
    for (var i = 0; i < numHairLines; i++) {
        pickedIndices.push(Math.floor(randomFromInterval(10, 180)));
    }
    pickedIndices.sort();
    for (var i = 0; i < numHairLines; i++) {
        var numHairPoints = 20 + Math.floor(randomFromInterval(-5, 5));
        // we generate some hair lines
        var hair_line = [];
        var index_offset = pickedIndices[i];
        var lower = randomFromInterval(0.8, 1.4);
        var reverse = Math.random() > 0.5 ? 1 : -1;
        for (var j = 0; j < numHairPoints; j++) {
            var powerscale = randomFromInterval(0.1, 3);
            var portion = (1 - (j / numHairPoints) ** powerscale) * (1 - lower) + lower;
            hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - (reverse * j + index_offset)) % faceCountourCopy.length][0] * portion, y: faceCountourCopy[(faceCountourCopy.length - (reverse * j + index_offset)) % faceCountourCopy.length][1] * portion });
        }
        var d = computeBezierCurve(hair_line, numHairPoints);
        if (Math.random() > 0.7) d = d.reverse();
        if (results.length == 0) {
            results.push(d);
            continue;
        }
        var lastHairPoint = results[results.length - 1][results[results.length - 1].length - 1];
        var lastPointsDistance = Math.sqrt((d[0][0] - lastHairPoint[0]) ** 2 + (d[0][1] - lastHairPoint[1]) ** 2);
        if (Math.random() > 0.5 && lastPointsDistance < 100) {
            results[results.length - 1] = results[results.length - 1].concat(d);
        } else {
            results.push(d);
        }
    }
    return results;
}

export function generateHairLines3(faceCountour: string | any[], numHairLines = 100) {
    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    var results = [];
    var pickedIndices = [];
    for (var i = 0; i < numHairLines; i++) {
        pickedIndices.push(Math.floor(randomFromInterval(10, 180)));
    }
    pickedIndices.sort();
    var splitPoint = Math.floor(randomFromInterval(0, 200));
    for (var i = 0; i < numHairLines; i++) {
        var numHairPoints = 30 + Math.floor(randomFromInterval(-8, 8));
        // we generate some hair lines
        var hair_line = [];
        var index_offset = pickedIndices[i];
        var lower = randomFromInterval(1, 2.3);
        if (Math.random() > 0.9) lower = randomFromInterval(0, 1.);
        var reverse = index_offset > splitPoint ? 1 : -1;
        for (var j = 0; j < numHairPoints; j++) {
            var powerscale = randomFromInterval(0.1, 3);
            var portion = (1 - (j / (numHairPoints)) ** powerscale) * (1 - lower) + lower;
            hair_line.push({ x: faceCountourCopy[(faceCountourCopy.length - (reverse * j * 2 + index_offset)) % faceCountourCopy.length][0] * portion, y: faceCountourCopy[(faceCountourCopy.length - (reverse * j * 2 + index_offset)) % faceCountourCopy.length][1] });
        }
        var d = computeBezierCurve(hair_line, numHairPoints);
        results.push(d);
    }
    return results;
}

export function generateMouthShape0(faceCountour: string | any[], faceHeight: number, faceWidth: number) {
    // the first one is a a big smile U shape
    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    // choose one point on face at bottom side
    var mouthRightY = randomFromInterval(faceHeight / 7, faceHeight / 3.5)
    var mouthLeftY = randomFromInterval(faceHeight / 7, faceHeight / 3.5)
    var mouthRightX = randomFromInterval(faceWidth / 10, faceWidth / 2)
    var mouthLeftX = -mouthRightX + randomFromInterval(-faceWidth / 20, faceWidth / 20)
    var mouthRight = [mouthRightX, mouthRightY]
    var mouthLeft = [mouthLeftX, mouthLeftY]

    var controlPoint0 = [randomFromInterval(0, mouthRightX), randomFromInterval(mouthLeftY + 5, faceHeight / 1.5)]
    var controlPoint1 = [randomFromInterval(mouthLeftX, 0), randomFromInterval(mouthLeftY + 5, faceHeight / 1.5)]

    var mouthPoints = []
    for (var i = 0; i < 1; i += 0.01) {
        mouthPoints.push(cubicBezier(mouthLeft, controlPoint1, controlPoint0, mouthRight, i))
    }
    if (Math.random() > 0.5) {
        for (var i = 0; i < 1; i += 0.01) {
            mouthPoints.push(cubicBezier(mouthRight, controlPoint0, controlPoint1, mouthLeft, i))
        }
    } else {
        var y_offset_portion = randomFromInterval(0, 0.8);
        for (var i = 0; i < 100; i += 1) {
            mouthPoints.push([mouthPoints[99][0] * (1 - i / 100.0) + mouthPoints[0][0] * i / 100.0, (mouthPoints[99][1] * (1 - i / 100.0) + mouthPoints[0][1] * i / 100.0) * (1 - y_offset_portion) + mouthPoints[99 - i][1] * y_offset_portion])
        }
    }
    return mouthPoints;
}

export function generateMouthShape1(faceCountour: string | any[], faceHeight: number, faceWidth: number) {
    // the first one is a a big smile U shape
    var faceCountourCopy = faceCountour.slice(0, faceCountour.length - 2);
    // choose one point on face at bottom side
    var mouthRightY = randomFromInterval(faceHeight / 7, faceHeight / 4)
    var mouthLeftY = randomFromInterval(faceHeight / 7, faceHeight / 4)
    var mouthRightX = randomFromInterval(faceWidth / 10, faceWidth / 2)
    var mouthLeftX = -mouthRightX + randomFromInterval(-faceWidth / 20, faceWidth / 20)
    var mouthRight = [mouthRightX, mouthRightY]
    var mouthLeft = [mouthLeftX, mouthLeftY]

    var controlPoint0 = [randomFromInterval(0, mouthRightX), randomFromInterval(mouthLeftY + 5, faceHeight / 1.5)]
    var controlPoint1 = [randomFromInterval(mouthLeftX, 0), randomFromInterval(mouthLeftY + 5, faceHeight / 1.5)]

    var mouthPoints = []
    for (var i = 0; i < 1; i += 0.01) {
        mouthPoints.push(cubicBezier(mouthLeft, controlPoint1, controlPoint0, mouthRight, i))
    }

    var center = [(mouthRight[0] + mouthLeft[0]) / 2, mouthPoints[25][1] / 2 + mouthPoints[75][1] / 2];
    if (Math.random() > 0.5) {
        for (var i = 0; i < 1; i += 0.01) {
            mouthPoints.push(cubicBezier(mouthRight, controlPoint0, controlPoint1, mouthLeft, i))
        }
    } else {
        var y_offset_portion = randomFromInterval(0, 0.8);
        for (var i = 0; i < 100; i += 1) {
            mouthPoints.push([mouthPoints[99][0] * (1 - i / 100.0) + mouthPoints[0][0] * i / 100.0, (mouthPoints[99][1] * (1 - i / 100.0) + mouthPoints[0][1] * i / 100.0) * (1 - y_offset_portion) + mouthPoints[99 - i][1] * y_offset_portion])
        }
    }
    // translate to center
    for (var i = 0; i < mouthPoints.length; i++) {
        mouthPoints[i][0] -= center[0]
        mouthPoints[i][1] -= center[1]
        // rotate 180 degree
        mouthPoints[i][1] = -mouthPoints[i][1]
        // scale smaller
        mouthPoints[i][0] = mouthPoints[i][0] * 0.6
        mouthPoints[i][1] = mouthPoints[i][1] * 0.6
        // translate back
        mouthPoints[i][0] += center[0]
        mouthPoints[i][1] += center[1] * 0.8
    }
    return mouthPoints;
}

export function generateMouthShape2(faceCountour: any, faceHeight: number, faceWidth: number) {
    // generate a random center
    var center = [randomFromInterval(-faceWidth / 8, faceWidth / 8), randomFromInterval(faceHeight / 4, faceHeight / 2.5)]

    var mouthPoints = getEggShapePoints(randomFromInterval(faceWidth / 4, faceWidth / 10), randomFromInterval(faceHeight / 10, faceHeight / 20), 0.001, 50);
    var randomRotationDegree = randomFromInterval(-Math.PI / 9.5, Math.PI / 9.5)
    for (var i = 0; i < mouthPoints.length; i++) {
        // rotate the point
        var x = mouthPoints[i][0]
        var y = mouthPoints[i][1]
        mouthPoints[i][0] = x * Math.cos(randomRotationDegree) - y * Math.sin(randomRotationDegree)
        mouthPoints[i][1] = x * Math.sin(randomRotationDegree) + y * Math.cos(randomRotationDegree)
        mouthPoints[i][0] += center[0]
        mouthPoints[i][1] += center[1]
    }
    return mouthPoints;
}